package net.pimpas.locking.serviceprovider;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;

@ServiceProviderAnnotation(instanceType=ServiceProviderInstanceType.SINGLETON,
		serviceProviderType=LockingServiceProvider.class)
public class LockingServiceProviderImpl implements LockingServiceProvider {
	
	protected static final Map<Object, ReentrantReadWriteLock> locks = 
		new HashMap<Object, ReentrantReadWriteLock>();
	
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(LockingServiceProviderImpl.class);
	
	protected boolean hasLock( Object object ) {
		return locks.containsKey(object);
		/*
		synchronized(locks) {
			Iterator<Object> it = locks.keySet().iterator();
			while(it.hasNext())
				if(it.next() == object) return true;
			return false;
		}
		*/
	}
	
	protected ReentrantReadWriteLock getLock( Object object ) {
		/*
		synchronized(locks) {
			Set<Entry<Object,ReentrantReadWriteLock>> set = locks.entrySet();
			Iterator<Entry<Object, ReentrantReadWriteLock>> iterator = set.iterator();
			Entry<Object, ReentrantReadWriteLock> entry = null;
			while(iterator.hasNext()) {
				entry = iterator.next();
				if(entry.getKey() == object)
					return entry.getValue();
			}
		}
		
		return null;
		*/
		return locks.get(object);
	}
	
	protected void createLock( Object object ) {
		ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
		
		synchronized(locks) {
			// double check :-)
			if(locks.containsKey(object)) return;
			logger.debug("[LockingProvider]: Creating lock for Object ["+object+"]");
			
			locks.put(object, lock);
		}
	}
	
	public void lockRead( Object object ) {
		if(!this.hasLock(object))
			this.createLock(object);
		
		ReentrantReadWriteLock lock = this.getLock(object);
		lock.readLock().lock();
		logger.debug("[LockingProvider]: Locking Object ["+object+"] for reading.");
	}
	
	public void unlockRead( Object object ) {
		ReentrantReadWriteLock lock = this.getLock(object);
		if(lock == null)
			return;
		
		if(lock.getReadHoldCount() == 0) return;
		
		lock.readLock().unlock();
		logger.debug("[LockingProvider]: Releasing read lock for Object ["+object+"]");
	}
	
	public void lockWrite( Object object ) {
		if(!this.hasLock(object))
			this.createLock(object);
		
		ReentrantReadWriteLock lock = this.getLock(object);
		lock.writeLock().lock();
		logger.debug("[LockingProvider]: Locking Object ["+object+"] for writing.");
	}
	
	public void unlockWrite( Object object ) {
		ReentrantReadWriteLock lock = this.getLock(object);
		if(lock == null)
			return;
		
		if(lock.getWriteHoldCount() == 0) return;
		
		lock.writeLock().unlock();
		logger.debug("[LockingProvider]: Releasing write lock for Object ["+object+"]");
	}
	
	public synchronized void releaseThreadLocks( ) {
		logger.debug("[LockingProvider]: Releasing all registered locks for Thread ["+Thread.currentThread()+"]");
		synchronized(locks) {
			Set<Object> objects = locks.keySet();
			Iterator<Object> it = objects.iterator();
			Object object = null;
			ReentrantReadWriteLock lock = null;
			final List<Object> remove = new ArrayList<Object>();
			while(it.hasNext()) {
				object = it.next();
				lock = this.getLock(object);
				while(lock.getWriteHoldCount() > 0)
					this.unlockWrite(object);
				while(lock.getReadHoldCount() > 0)
					this.unlockRead(object);
				if(lock.getQueueLength()==0 && !lock.hasQueuedThreads() && lock.getReadLockCount()==0 && !lock.isWriteLocked()) remove.add(object);
			}
			
			for(Object obj: remove) {
				lock = this.getLock(object);
					if(lock != null && lock.getQueueLength()==0 && !lock.hasQueuedThreads() && lock.getReadLockCount()==0 && !lock.isWriteLocked())
						locks.remove(obj);
			}
			
		}
		
		logger.debug("[LockingProvider]: All thread locks released!");
	}

	public void config() throws ConfigurationException {
		logger.info("[LockingProvider]: Initializing LockingServiceProvider....");
	}
	
	public boolean isTerminated() {
		return false;
	}
}